package com.ltcode.auth.application.service;

import com.ltcode.auth.application.dto.LoginCommand;
import com.ltcode.auth.application.dto.LoginResult;
import com.ltcode.auth.application.dto.RegisterCommand;
import com.ltcode.auth.domain.model.AuthUser;
import com.ltcode.auth.domain.model.AuthToken;
import com.ltcode.auth.domain.model.LoginSession;
import com.ltcode.auth.domain.repository.AuthUserRepository;
import com.ltcode.auth.domain.repository.AuthTokenRepository;
import com.ltcode.auth.domain.repository.LoginSessionRepository;
import com.ltcode.auth.domain.service.PasswordService;
import com.ltcode.auth.domain.service.JwtTokenService;
import com.ltcode.common.base.constant.Constants;
import com.ltcode.common.base.exception.BizException;
import com.ltcode.common.base.utils.ip.IpUtils;
import com.ltcode.log.starter.annotation.BizLog;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;

/**
 * 认证应用服务
 * 
 * @author ltcode
 * @since 2024-01-01
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class AuthApplicationService {

    private final AuthUserRepository authUserRepository;
    private final AuthTokenRepository authTokenRepository;
    private final LoginSessionRepository loginSessionRepository;
    private final PasswordService passwordService;
    private final JwtTokenService jwtTokenService;
    private final CaptchaApplicationService captchaApplicationService;



    /**
     * 用户登录
     */
    @Transactional
    @BizLog(name = "用户登录", biz_plat = Constants.BIZ_PLAT_AUTH)
    public LoginResult login(LoginCommand command) {
        log.info("用户登录: username={}", command.getUsername());

        // 1. 验证验证码
        captchaApplicationService.validateCaptcha(command.getCaptchaCode(), command.getCaptchaUuid());
        // 2. 查询用户信息
        Optional<AuthUser> userOpt = authUserRepository.findByUsername(command.getUsername());
        if (!userOpt.isPresent()) {
            throw new BizException("用户名或密码错误");
        }
        AuthUser user = userOpt.get();
        // 3. 检查用户状态
        user.checkCanLogin();

        // 4. 验证密码
        if (!passwordService.matches(command.getPassword(), user.getPassword())) {
            throw new BizException("用户名或密码错误");
        }

        // 5. 更新用户登录信息
        String loginIp = IpUtils.getIpAddr();
        user.updateLoginInfo(loginIp);
        authUserRepository.updateLoginInfo(user.getId(), loginIp);

        // 6. 创建令牌
        String accessTokenId = UUID.randomUUID().toString();

        AuthToken accessToken = AuthToken.createAccessToken(
                accessTokenId, user.getId(), user.getUsername(), loginIp, Constants.ACCESS_TOKEN_EXPIRE_MINUTES);

        // 7. 保存令牌
        authTokenRepository.save(accessToken);

        // 8. 创建登录会话
        String sessionId = UUID.randomUUID().toString();
        LoginSession session = LoginSession.create(sessionId, user, accessToken, refreshToken, loginIp);
        loginSessionRepository.save(session);

        // 9. 生成JWT令牌
        Map<String, Object> claims = new HashMap<>();
        claims.put("user_key", accessTokenId);
        claims.put("user_id", user.getId());
        claims.put("username", user.getUsername());

        String jwtToken = jwtTokenService.createToken(claims);

        // 10. 构建返回结果
        LoginResult result = new LoginResult();
        result.setAccessToken(jwtToken);
        result.setTokenType(Constants.LOGIN_TYPE);
        result.setExpiresIn(Constants.ACCESS_TOKEN_EXPIRE_MINUTES * 60); // 转换为秒
        result.setRefreshToken(refreshTokenId);
        result.setUserId(user.getId());
        result.setUsername(user.getUsername());
        result.setNickName(user.getNickName());
        result.setAvatar(user.getAvatar());
        result.setPermissions(user.getPermissions());
        result.setRoles(user.getRoles());


        log.info("用户登录成功: username={}, userId={}", command.getUsername(), user.getId());
        return result;
    }

    /**
     * 用户注册
     */
    @Transactional
    @BizLog(name = "用户注册", biz_plat = Constants.BIZ_PLAT_AUTH)
    public void register(RegisterCommand command) {
        log.info("用户注册: username={}", command.getUsername());

        // 1. 验证验证码
        captchaApplicationService.validateCaptcha(command.getCaptchaCode(), command.getCaptchaUuid());

        // 2. 检查用户名是否已存在
        if (authUserRepository.existsByUsername(command.getUsername())) {
            throw new BizException("用户名已存在");
        }

        // 3. 检查邮箱是否已存在
        if (command.getEmail() != null && authUserRepository.existsByEmail(command.getEmail())) {
            throw new BizException("邮箱已存在");
        }

        // 4. 检查手机号是否已存在
        if (command.getPhoneNumber() != null && authUserRepository.existsByPhoneNumber(command.getPhoneNumber())) {
            throw new BizException("手机号已存在");
        }

        // 5. 创建用户
        AuthUser user = new AuthUser();
        user.setUsername(command.getUsername());
        user.setNickName(command.getNickName() != null ? command.getNickName() : command.getUsername());
        user.setPassword(passwordService.encryptPassword(command.getPassword()));
        user.setEmail(command.getEmail());
        user.setPhoneNumber(command.getPhoneNumber());
        user.setStatus("0"); // 正常状态
        user.setDelFlag("0"); // 未删除
        user.setUserType("00"); // 系统用户

        // 6. 保存用户
        authUserRepository.save(user);

        log.info("用户注册成功: username={}", command.getUsername());
    }

    /**
     * 用户登出
     */
    @Transactional
    @BizLog(name = "用户登出", biz_plat = Constants.BIZ_PLAT_AUTH)
    public void logout(String accessToken) {
        log.info("用户登出: accessToken={}", accessToken);

        // 1. 解析JWT令牌获取用户键
        String userKey = jwtTokenService.getUserKeyFromToken(accessToken);
        if (userKey == null) {
            log.warn("无效的访问令牌: {}", accessToken);
            return;
        }

        // 2. 查询令牌信息
        Optional<AuthToken> tokenOpt = authTokenRepository.findByTokenId(userKey);
        if (!tokenOpt.isPresent()) {
            log.warn("令牌不存在: {}", userKey);
            return;
        }

        AuthToken token = tokenOpt.get();

        // 3. 撤销用户的所有令牌
        authTokenRepository.revokeAllTokensByUserId(token.getUserId());

        // 4. 查询并登出会话
        Optional<LoginSession> sessionOpt = loginSessionRepository.findByAccessToken(userKey);
        if (sessionOpt.isPresent()) {
            LoginSession session = sessionOpt.get();
            session.logout();
            loginSessionRepository.save(session);
        }

        log.info("用户登出成功: userId={}", token.getUserId());
    }

    /**
     * 刷新令牌
     */
    @Transactional
    public LoginResult refreshToken(String refreshTokenId) {
        log.info("刷新令牌: refreshTokenId={}", refreshTokenId);

        // 1. 查询刷新令牌
        Optional<AuthToken> refreshTokenOpt = authTokenRepository.findByTokenId(refreshTokenId);
        if (!refreshTokenOpt.isPresent()) {
            throw new BizException("刷新令牌不存在");
        }

        AuthToken refreshToken = refreshTokenOpt.get();

        // 2. 检查刷新令牌是否有效
        if (!refreshToken.isValid()) {
            throw new BizException("刷新令牌无效或已过期");
        }

        // 3. 查询用户信息
        Optional<AuthUser> userOpt = authUserRepository.findById(refreshToken.getUserId());
        if (!userOpt.isPresent()) {
            throw new BizException("用户不存在");
        }

        AuthUser user = userOpt.get();

        // 4. 检查用户状态
        user.checkCanLogin();

        // 5. 创建新的访问令牌
        String newAccessTokenId = UUID.randomUUID().toString();
        AuthToken newAccessToken = AuthToken.createAccessToken(
                newAccessTokenId, user.getId(), user.getUsername(),
                refreshToken.getLoginIp(), Constants.ACCESS_TOKEN_EXPIRE_MINUTES);

        // 6. 保存新令牌
        authTokenRepository.save(newAccessToken);

        // 7. 更新会话
        Optional<LoginSession> sessionOpt = loginSessionRepository.findByAccessToken(refreshToken.getTokenId());
        if (sessionOpt.isPresent()) {
            LoginSession session = sessionOpt.get();
            session.refreshAccessToken(newAccessToken);
            loginSessionRepository.save(session);
        }

        // 8. 生成新的JWT令牌
        Map<String, Object> claims = new HashMap<>();
        claims.put("user_key", newAccessTokenId);
        claims.put("user_id", user.getId());
        claims.put("username", user.getUsername());

        String jwtToken = jwtTokenService.createToken(claims);

        // 9. 构建返回结果
        LoginResult result = new LoginResult();
        result.setAccessToken(jwtToken);
        result.setTokenType(Constants.LOGIN_TYPE);
        result.setExpiresIn(Constants.ACCESS_TOKEN_EXPIRE_MINUTES * 60);
        result.setRefreshToken(refreshTokenId); // 保持原刷新令牌
        result.setUserId(user.getId());
        result.setUsername(user.getUsername());
        result.setNickName(user.getNickName());
        result.setAvatar(user.getAvatar());

        log.info("刷新令牌成功: userId={}", user.getId());
        return result;
    }

    /**
     * 获取当前用户信息
     */
    public AuthUser getCurrentUser(String accessToken) {
        try {
            // 1. 解析JWT令牌获取用户键
            String userKey = jwtTokenService.getUserKeyFromToken(accessToken);
            if (userKey == null) {
                throw new BizException("无效的访问令牌");
            }

            // 2. 查询令牌信息
            Optional<AuthToken> tokenOpt = authTokenRepository.findByTokenId(userKey);
            if (!tokenOpt.isPresent()) {
                throw new BizException("令牌不存在");
            }

            AuthToken token = tokenOpt.get();

            // 3. 检查令牌是否有效
            if (!token.isValid()) {
                throw new BizException("令牌无效或已过期");
            }

            // 4. 查询用户信息
            Optional<AuthUser> userOpt = authUserRepository.findById(token.getUserId());
            if (!userOpt.isPresent()) {
                throw new BizException("用户不存在");
            }

            AuthUser user = userOpt.get();

            // 5. 获取用户权限和角色
            Set<String> permissions = authUserRepository.findPermissionsByUserId(user.getId());
            Set<String> roles = authUserRepository.findRolesByUserId(user.getId());

            user.setPermissions(permissions);
            user.setRoles(roles);

            return user;
        } catch (Exception e) {
            log.error("获取当前用户信息失败: accessToken={}", accessToken, e);
            throw new RuntimeException("获取用户信息失败", e);
        }
    }
}
